package cn.ava.basic.service.impl;

import cn.ava.basic.constant.BaseConstants;
import cn.ava.basic.dto.LoginDto;
import cn.ava.basic.dto.WeChartDto;
import cn.ava.basic.exception.BusinessException;
import cn.ava.basic.jwt.JwtUtils;
import cn.ava.basic.jwt.RsaUtils;
import cn.ava.basic.jwt.UserInfo;
import cn.ava.basic.service.ILoginService;
import cn.ava.basic.utils.*;
import cn.ava.system.domain.Menu;
import cn.ava.system.mapper.MenuMapper;
import cn.ava.system.mapper.PermissionMapper;
import cn.ava.user.domain.Logininfo;
import cn.ava.user.domain.User;
import cn.ava.user.domain.Wxuser;
import cn.ava.user.mapper.LogininfoMapper;
import cn.ava.user.mapper.UserMapper;
import cn.ava.user.mapper.WxuserMapper;
import com.alibaba.fastjson.JSONObject;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import java.security.PrivateKey;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static cn.ava.basic.jwt.JwtUtils.generateTokenExpireInMinutes;

@Service
// 开启事务 只读属性和支持事务
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class LoginServiceImpl implements ILoginService {

    @Autowired // logininfo登录信息map层
    private LogininfoMapper logininfoMapper;

    @Autowired // redis连接
    private RedisTemplate<Object,Object> redisTemplate;

    @Autowired // 微信用户map层
    private WxuserMapper wxuserMapper;

    @Autowired // 用户map层
    private UserMapper userMapper;

    @Autowired // 权限管理
    private PermissionMapper permissionMapper;

    @Autowired // 菜单权限管理
    private MenuMapper menuMapper;

    @Value("${jwt.rsa.pri}") // 私钥
    private String jwtPrivateKey;

    /**
     * @Title: accountLogin登录方案
     * @Description: 账号密码登录业务
     * @Author: Avavaava
     * @Version: 23.53
     * @Date: 2022/7/11 9:37
     * @param:
     * @return:
     */
    @Override
    public Map<String, Object> accountLogin(LoginDto loginDto) {
        // 先获取参数
        String username = loginDto.getUsername();
        String password = loginDto.getPassword();
        String type = loginDto.getType();
        // 调用非空校验工具类
        CheckCodeUtil.checkEmpty(username,password,type);
        // 查询出是否有这个用户
        Logininfo logininfo = logininfoMapper.getByUser(loginDto);
        if (logininfo == null){
            throw new BusinessException("用户名或密码错误!,请重新输入");
        }
        // 再判断密码是否一致,先给用户输入的密码进行加盐加密,再进行对比
        String md5Pwd = MD5Utils.encrypByMd5(logininfo.getSalt() + password);
        if (!logininfo.getPassword().equals(md5Pwd)) {
            throw new BusinessException("用户名或密码错误!,请重新输入");
        }
        // 返回成功登录信息
        return loginSuccess(logininfo);
    }


    /**
     * @Title: phoneLogin
     * @Description: 手机号登录业务
     * @Author: Avavaava
     * @Version: 23.53
     * @Date: 2022/7/11 9:37
     * @param:
     * @return:
     */
    @Override
    public Map<String, Object> phoneLogin(LoginDto loginDto) {
        // 用户填写的手机号
        String phone = loginDto.getPhone();
        String type = loginDto.getType(); // 用户类型
        String smsCode = loginDto.getSmsCodeValue(); // 短信验证码
        // 调用非空校验工具类
        CheckCodeUtil.checkEmpty(phone,type,smsCode);
        // 调用短信验证码校验工具类
        CheckCodeUtil.checkSmsCode(redisTemplate
                ,BaseConstants.VerifyCodeConstant.BUSINESS_LOGIN_KEY
                ,phone
                ,smsCode);
        // 查询用户是否存在
        Logininfo logininfo = logininfoMapper.getByPhone(loginDto);
        if (logininfo == null) {
            throw new BusinessException("用户不存在,请进行注册!");
        }
        // 返回成功登录信息
        return loginSuccess(logininfo);
    }

    /**
     * @Title: wechatLogin
     * @Description: 微信第三方扫码登录
     * @Author: Avavaava
     * @Version: 23.53
     * @Date: 2022/7/11 9:38
     * @param:
     * @return:
     */
    @Override
    public AjaxResult wechatLogin(WeChartDto weChartDto) {
        String code = weChartDto.getCode();
        // 判断非空
        if (StringUtils.isEmpty(code)){
            throw new BusinessException("参数异常!,请重试");
        }
        //  通过code参数加上AppId 和Secret等，通过API换取access_token
        String url = BaseConstants.WeChatConstant.WECHART_TOKEN_URL
                .replace("APPID", BaseConstants.WeChatConstant.WECHART_APPID)
                .replace("SECRET", BaseConstants.WeChatConstant.WECHART_SECRET)
                .replace("CODE",code);
        // 通过API换取到access_token和openid
        String jsonStr = HttpUtil.httpGet(url);
        // 把jsonStr转换为JSON对象
        JSONObject jsonObject = JSONObject.parseObject(jsonStr);
        // 此对象中有Access_token和openid参数,直接获取
        String accessToken = jsonObject.getString("access_token");
        String openId = jsonObject.getString("openid");
        // 根据openId查询数据库中有没有此数据
        Wxuser wxuser = wxuserMapper.getByOpenId(openId);
        // 如果wxuser不为空且userid也为空,说明已经绑定注册过账号了
        if (wxuser != null && wxuser.getUserId() != null) { // 直接免密登录
            // 根据userid查询出登录中间表中的数据,进行登录成功业务逻辑
            Logininfo logininfo = logininfoMapper.getByUserId(wxuser.getUserId());
            // 返回成功登录信息
            Map<String, Object> map = loginSuccess(logininfo);
            return new AjaxResult().setResultObj(map);
        }
        // 不存在进行账号绑定,拼接一个字符串,传递参数到前端进行回调
        StringBuilder param = new StringBuilder();
        param.append("?accessToken=")
                .append(accessToken)
                .append("&openId=")
                .append(openId);
        // 说明此用户没有绑定过账号,直接返回false和失败信息
        return new AjaxResult()
                .setSuccess(false)
                .setMessage("noBinder")
                .setResultObj(param);

    }


    /**
     * @Title: wechatBinder
     * @Description: 微信用户绑定注册平台账号
     * @Author: Avavaava
     * @Version: 23.53
     * @Date: 2022/7/26 16:21
     * @param:
     * @return:
     */
    @Override
    @Transactional
    public Map<String,Object> wechatBinder(WeChartDto weChartDto) {
        // 先获取参数
        String phone = weChartDto.getPhone();
        String verifyCode = weChartDto.getVerifyCode();
        String accessToken = weChartDto.getAccessToken();
        String openId = weChartDto.getOpenId();
        // 调用非空校验工具类
        CheckCodeUtil.checkEmpty(phone,verifyCode,accessToken,openId);
        // 调用短信验证码校验工具类
        CheckCodeUtil.checkSmsCode(redisTemplate
                ,BaseConstants.VerifyCodeConstant.BUSINESS_BINDER_KEY
                ,phone
                ,verifyCode);
        // 根据手机号查询用户是否存在数据库t_user表中
        User user = userMapper.getByPhone(phone);
        Logininfo logininfo = null;
        if (user == null) {
            // user已经不为空，直接使用user对象
            user = wechart2User(phone); // 初始化user对象
            // 将user数据拷贝到Logininfo中
            logininfo = user2Logininfo(user);
            logininfoMapper.add(logininfo);
            user.setLogininfoId(logininfo.getId());
            userMapper.add(user);
        }else {
            // 如果不为空,那么就是存在
            logininfo = logininfoMapper.getByUserId(user.getId());
        }
        // 根据URL地址常量替换参数获取ACCESS_TOKEN和openid
        String url = BaseConstants.WeChatConstant.WECHART_USERINFO_URL
                .replace("ACCESS_TOKEN", accessToken)
                .replace("OPENID", openId);
        // 通过API换取到用户信息
        String jsonStr = HttpUtil.httpGet(url);
        // 再判断是否获取到用户信息
        if (StringUtils.isEmpty(jsonStr)) {
            throw new BusinessException("参数异常!,请重试");
        }
        // 把获取到的JSON字符串转换为对象
        Wxuser wxuser = JSONObject.parseObject(jsonStr, Wxuser.class);
        // 将user对象的id设置进入wxuser对象中的userId字段中
        wxuser.setUserId(user.getId());
        // 保存数据
        wxuserMapper.add(wxuser);
        // 返回成功登录信息
        return loginSuccess(logininfo);

    }

//    /**
//     * @Title: loginSuccess
//     * @Description: 登录成功业务逻辑(无状态Token方案)
//     * @Author: Avavaava
//     * @Version: 23.53
//     * @Date: 2022/7/11 12:01
//     * @param:
//     * @return:
//     */
//    public Map<String, Object> loginSuccess(Logininfo logininfo) {
//        // 生成UUID作为token存入redis中,并设置三十分钟有效期
//        String token = UUID.randomUUID().toString();
//        // token作为key,logininfo作为value,注意向redis存入对象需要实现Serializable接口
//        redisTemplate.opsForValue().set(token,logininfo,30, TimeUnit.MINUTES);
//        // 把logininfo重要的参数置为空
//        logininfo.setPassword("");
//        logininfo.setSalt("");
//        // 把token和用户信息放入map中
//        HashMap<String, Object> map = new HashMap<>();
//        map.put("token",token);
//        map.put("logininfo",logininfo);
//        return map;
//    }

    /**
     * @Title: loginSuccess
     * @Description: 登录成功业务逻辑(jwt方案)
     * @Author: Avavaava
     * @Version: 23.53
     * @Date: 2022/7/11 12:01
     * @param:
     * @return:
     */
    public Map<String, Object> loginSuccess(Logininfo logininfo) {
        // 1 使用私钥加密,获取到token
        PrivateKey privateKey = RsaUtils.getPrivateKey(JwtUtils.class.getClassLoader().getResource(jwtPrivateKey).getFile());
        UserInfo userInfo = new UserInfo();
        // 设置用户信息进入我们需要封装的JWT中
        userInfo.setLogininfo(logininfo);
        // 创建一个map用于返回给前端
        HashMap<String, Object> map = new HashMap<>();
        // 判断是否是后台管理员
        if (logininfo.getType() == 0){
            // 查询出管理员拥有的权限
            List<String> permissions =  logininfoMapper.getOwnPermissionByLoginfoId(logininfo.getId());
            // 查询出菜单权限
            List<Menu> menus = menuMapper.getOwnMenuByLogininId(logininfo.getId());
            // 封装进与用户信息
            userInfo.setPermissions(permissions);
            userInfo.setMenus(menus);
            // 设置进map
            map.put("permissions",permissions);
            map.put("menus",menus);
        }
        String token = generateTokenExpireInMinutes(userInfo, privateKey, 9999);
        // 把logininfo重要的参数置为空
        logininfo.setPassword("");
        logininfo.setSalt("");
        // 把token和用户信息放入map中
        map.put("token",token);
        map.put("logininfo",logininfo);
        return map;
    }

    /**
     * @Title: wechart2User
     * @Description: 初始化user对象
     * @Author: Avavaava
     * @Version: 23.53
     * @Date: 2022/7/11 12:02
     * @param:
     * @return:
     */
    public User wechart2User(String phone) {
        // 获取user对象
        User user = new User();
        // 设置属性
        user.setUsername(phone);
        user.setPhone(phone);
        // 获取盐值
        String salt = StrUtils.getComplexRandomString(32);
        // 获取MD5进行加盐加密
        String md5Password = MD5Utils.encrypByMd5(salt + "123456");
        user.setSalt(salt);
        user.setPassword(md5Password);
        // 发送短信提示用户初始密码
        // SendSmsUtil.SendSms(phone,"尊敬的用户您好,\n您的初始密码为: " + "123456");
        return user;
    }

    /**
     * @Title: user2Logininfo
     * @Description: 使用user对象初始化logininfo对象
     * @Author: Avavaava
     * @Version: 23.53
     * @Date: 2022/7/11 12:02
     * @param:
     * @return:
     */
    private Logininfo user2Logininfo(User user) {
        Logininfo logininfo = new Logininfo();
        BeanUtils.copyProperties(user,logininfo);
        logininfo.setType(1);
        return logininfo;
    }


}
